﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace SafeScript.Generator
{
    public class JavaScriptWriter
    {
        protected StreamWriter stream;
        private bool lastWasIdentifier = false;
        private int indent = 0;

        public JavaScriptWriter(StreamWriter stream)
        {
            this.stream = stream;
        }

        protected JavaScriptWriter()
        {
        }

        public virtual void WriteIdentifier(string ident)
        {
            if (this.lastWasIdentifier)
                this.stream.Write(' ');
            this.stream.Write(ident);
            this.lastWasIdentifier = true;
        }

        public virtual void WriteString(string value)
        {
            // TODO: escaping
            this.stream.Write('"');
            this.stream.Write(value);
            this.stream.Write('"');
            this.lastWasIdentifier = false;
        }

        public virtual void WriteInt(int value)
        {
            if (this.lastWasIdentifier)
                this.stream.Write(' ');
            this.stream.Write(value);
            this.lastWasIdentifier = true;
        }

        public virtual void WriteFloat(double value)
        {
            if (this.lastWasIdentifier)
                this.stream.Write(' ');
            this.stream.Write(value);
            this.lastWasIdentifier = true;
        }

        public virtual void WriteBool(bool value)
        {
            if (this.lastWasIdentifier)
                this.stream.Write(' ');
            this.stream.Write(value ? "true" : "false");
            this.lastWasIdentifier = true;
        }

        public virtual void WriteOperator(string op)
        {
            if (op == "}")
            {
                this.stream.WriteLine();
                this.indent--;
                this.WriteIndent();
            }
            else if (op == "{")
            {
                this.stream.WriteLine();
                this.WriteIndent();
            }
            this.stream.Write(op);
            this.lastWasIdentifier = false;
            if (op == "}")
            {
                this.stream.WriteLine();
                this.WriteIndent();
            }
            else if (op == "{")
            {
                this.indent++;
                this.stream.WriteLine();
                this.WriteIndent();
            }
            else if (op == ";")
            {
                this.stream.WriteLine();
                this.WriteIndent();
            }
        }

        public virtual void WriteLine()
        {
            this.stream.WriteLine();
            this.WriteIndent();
        }

        public virtual void Write(BufferedJavaScriptWriter writer)
        {
            this.stream.Write(writer.ToString());
        }

        public virtual void Write(StringList lst)
        {
            foreach (string str in lst)
            {
                var dummy = this + str;
            }
        }

        private void WriteIndent()
        {
            for (int i = 0; i < this.indent; ++i)
                this.stream.Write('\t');
        }

        public static JavaScriptWriter operator+(JavaScriptWriter writer, string str)
        {
            if (char.IsLetterOrDigit(str[0]) || str[0] == '_' || str[0] == '$')
                writer.WriteIdentifier(str);
            else
                writer.WriteOperator(str);
            return writer;
        }
        
        public static JavaScriptWriter operator +(JavaScriptWriter writer, StringList value)
        {
            writer.Write(value);
            return writer;
        }

        public static JavaScriptWriter operator +(JavaScriptWriter writer, int value)
        {
            writer.WriteInt(value);
            return writer;
        }

        public static JavaScriptWriter operator +(JavaScriptWriter writer, double value)
        {
            writer.WriteFloat(value);
            return writer;
        }

        public static JavaScriptWriter operator +(JavaScriptWriter writer, bool value)
        {
            writer.WriteBool(value);
            return writer;
        }
    }

    public class BufferedJavaScriptWriter : JavaScriptWriter
    {
        private MemoryStream memStream;

        public BufferedJavaScriptWriter()
        {
            this.memStream = new MemoryStream();
            this.stream = new StreamWriter(this.memStream);
        }

        public override string ToString()
        {
            this.stream.Close();
            return System.Text.Encoding.UTF8.GetString(this.memStream.ToArray());
        }
    }

    public class CaseLabel
    {
        internal CaseLabel()
        {
        }

        internal BufferedJavaScriptIteratorWriter.Case Position;
    }

    public class BufferedJavaScriptIteratorWriter : BufferedJavaScriptWriter
    {
        private int labels = 0;
        private BufferedJavaScriptIteratorWriter parent;
        private CaseLabel currentLabel;

        public BufferedJavaScriptIteratorWriter()
        {
        }

        public BufferedJavaScriptIteratorWriter(BufferedJavaScriptIteratorWriter parent)
        {
            this.parent = parent;
        }

        internal class Case
        {
            internal int Number;
            internal bool IsReferenced;
            internal bool IsFixed;
        }

        public class StringItem
        {
            public string String;

            public StringItem(string item)
            {
                this.String = item;
            }
        }

        private List<object> items = new List<object>();

        public void Write(BufferedJavaScriptIteratorWriter writer)
        {
            this.currentLabel = null;
            this.items.AddRange(writer.items);
        }

        public override void Write(BufferedJavaScriptWriter writer)
        {
            this.currentLabel = null;
            this.items.Add(writer);
        }

        public override void Write(StringList lst)
        {
            this.currentLabel = null;
            this.items.Add(lst);
        }

        public override void WriteBool(bool value)
        {
            this.currentLabel = null;
            this.items.Add(value);
        }

        public override void WriteFloat(double value)
        {
            this.currentLabel = null;
            this.items.Add(value);
        }

        public override void WriteIdentifier(string ident)
        {
            this.currentLabel = null;
            this.items.Add(ident);
        }

        public override void WriteInt(int value)
        {
            this.currentLabel = null;
            this.items.Add(value);
        }

        public override void WriteOperator(string op)
        {
            this.currentLabel = null;
            this.items.Add(op);
        }

        public override void WriteString(string value)
        {
            this.currentLabel = null;
            this.items.Add( new StringItem(value) );
        }

        public override void WriteLine()
        {
            this.currentLabel = null;
            this.items.Add(null);
        }

        public void Write(CaseLabel label)
        {
            this.currentLabel = null;
            label.Position.IsReferenced = true;
            this.items.Add(label);
        }

        public CaseLabel CreateLabel()
        {
            BufferedJavaScriptIteratorWriter p = this;
            while (p.parent != null)
                p = p.parent;

            var c = new Case() { Number = p.labels++ };
            return new CaseLabel() { Position = c };
        }

        public void CreateInitialLabel()
        {
            CaseLabel l = this.CreateLabel();
            l.Position.IsReferenced = true;
            this.FixLabel(l);
        }

        public void FixLabel(CaseLabel label, bool force)
        {
            System.Diagnostics.Debug.Assert(label.Position.IsFixed == false, "Fixed a label twice");
            if (this.currentLabel != null)
            {
                label.Position = this.currentLabel.Position;
                if (force)
                    label.Position.IsReferenced = true;                
                return;
            }

            if (force)
                label.Position.IsReferenced = true;
            label.Position.IsFixed = true;
            this.currentLabel = label;
            this.items.Add(label.Position);
        }

        public void FixLabel(CaseLabel c)
        {
            this.FixLabel(c, false);
        }

        public override string ToString()
        {
            foreach (object obj in this.items)
            {
                if (obj is BufferedJavaScriptWriter)
                    base.Write(obj as BufferedJavaScriptWriter);
                else if (obj is StringList)
                    base.Write(obj as StringList);
                else if (obj is bool)
                    base.WriteBool((bool)obj);
                else if (obj is double)
                    base.WriteFloat((double)obj);
                else if (obj is string)
                {
                    var str = obj as string;
                    if (char.IsLetterOrDigit(str[0]) || str[0] == '_' || str[0] == '$')
                        base.WriteIdentifier(str);
                    else
                        base.WriteOperator(str);
                }
                else if (obj is int)
                    base.WriteInt((int)obj);
                else if (obj is StringItem)
                    base.WriteString((obj as StringItem).String);
                else if (obj == null)
                    base.WriteLine();
                else if (obj is Case )
                {
                    if ((obj as Case).IsReferenced)
                    {
                        base.WriteIdentifier("case");
                        base.WriteInt((obj as Case).Number);
                        base.WriteOperator(":");
                    }
                }
                else if (obj is CaseLabel)
                {
                    System.Diagnostics.Debug.Assert((obj as CaseLabel).Position.IsFixed, "Case label has not been fixed");
                    base.WriteInt((obj as CaseLabel).Position.Number);
                }
                else
                    System.Diagnostics.Debug.Assert(false, "Programming error");
            }

            return base.ToString();
        }
    }
}
